<?php
/**
 * 
 * @file 3gplex/includes/content.inc
 * 
 * Content (CCK) contrib module configuration API.
 */

require_once('./'. drupal_get_path('module', 'drupalplex') .'/includes/module.inc');
require_once('./'. drupal_get_path('module', 'content') .'/content_crud.inc');

/**
 * Content (CCK) contrib module wrapper.
 * 
 * For an overview of CCK refer to @ref plex_content_page and @ref plex_content along with plex_content.
 * 
 * CCK provides the file content_crud.inc (CRUD means Create/Read/Update/Delete).
 * However it lacks a good number of functionalities that stay as todos. To
 * permit CCK fields integration in UBICAST installation profiles this library
 * include file permits to programmatically create and delete CCK fields
 * definitions and instances.
 */
class plex_content extends plex_module
{
	function get_description()
	{
		return "Configures and loads API for Drupal contrib content module: Custom fields for any content type";
	}
	/**
	 * @name CCK Toolbox
	 *
	 * CCK provides a toolbox. However it lacks some useful components you will find here.
	 * Also cckck does not use the exact same ways. E.g. the field names should be given
	 * as plain non-limited strings as they always are straightened internally by a call
	 * to cckck_field_name().
	 *
	 * @todo Wrap the tools already provided with CCK for consistency.
	 */
	/**
	 * @{
	 */
	
	/**
	 * Verifies validity of a CCK field name.
	 */
	function validate_field_name($field_name)
	{
		$fields = content_fields();
		if (!empty($fields[$field_name])) {
			return array(
			'success'=>false,
			'message'=>'The field name %field_name already exists.',
			'arguments'=>array('%field_name' => $field_name)
			);
		}
		if (!preg_match('!^field_[a-z0-9_]+$!', $field_name)) {
			return array(
			'success'=>false,
			'message'=>'The field name %field_name is invalid.',
			'arguments'=>array('%field_name' => $field_name)
			);
		}
		return array(
			'success'=>true,
			'message'=>"%field_name is a valid cck field name",
			'arguments'=>array('%field_name'=>$field_name)
		);
	}
	
	/**
	 * Returns a valid CCK field name given a string.
	 */
	function field_name($field_name)
	{
		$field_name = trim($field_name);
		$field_name = drupal_strtolower($field_name);
		$field_name = str_replace(array(' ', '-'), '_', $field_name);
		$field_name = preg_replace('/[^a-z0-9_]/', '', $field_name);
		$field_name = 'field_'. $field_name;
		$field_name = substr($field_name, 0, 31);
		return $field_name;
	}

	/**
	 * Returns a valid and unused field name given a string.
	 * 
	 * @note
	 * This API gets unstraightened field names as input so names
	 * this function returns may refer to fields this API does not
	 * handle.
	 */
	function new_field_name($field_name, $straight = false)
	{
		$field_name = $straight ? $field_name : field_name($field_name);
		$fields = content_fields();
		if (isset($fields[$field_name])) {
			$counter = 0;
			do { $new_name = substr($field_name, 0, 29) .'_'. $counter++;
			} while (isset($fields[$new_name]));
			$field_name = $new_name;
		}
		return $field_name;
	}

	/**
	 * Returns true if a CCK field with the given (unstraightened) name exists, false otherwise.
	 *
	 * @arg $field_name will be converted internally by cckck_field_name.
	 */
	function field_exists($field_name, $straight = false)
	{
		return (content_fields($straight ? $field_name : field_name($field_name)) === null) ? false : true;
	}
	
	/**
	 * Returns CCK informations for given (unstraightened) field name.
	 * 
	 * @arg $field_name will be converted internally by cckck_field_name.
	 */
	function field_info($field_name, $straight = false)
	{
		return content_fields($straight ? $field_name : field_name($field_name));
	}
	
	/**
	 * Returns true if a CCK field type with the given name exists, false otherwise.
	 */
	function field_type_exists($field_type)
	{
		$field_types = _content_field_types();
		return isset($field_types[$field_type])?true:false;
	}
	
	/**
	 * Returns CCK informations for given field type name.
	 */
	function field_type_info($field_type)
	{
		$field_types = _content_field_types();
		return isset($field_types[$field_type]) ? $field_types[$field_type] : null;
	}
	
	/**
	 * Returns name of module providing $field_type or null if $field_type doesn't exist.
	 */
	function field_type_module($field_type)
	{
		$field_type_info = $this->field_type_info($field_type);
		return isset($field_type_info) ? $field_type_info['module'] : null; 
	}
	
	private function cck_taxonomy_ssu_widget_type_name($vocabulary_name)
	{
		$vocabularies = taxonomy_get_vocabularies();
		foreach ($vocabularies as $vocabulary) {
			if (trim(strtolower($vocabulary->name)) === trim(strtolower($vocabulary_name))) {
				return "cck_taxonomy_ssu_".(int)$vocabulary->vid;
			}
		}
		plex::warn("plex_content::cck_taxonomy_ssu_widget_type(): Invalid widget type as there's no vocabulary".
		" named '%vocabulary'", array('%vocabulary'=>$vocabulary_name));
		return "cck_taxonomy_ssu_".$vocabulary_name;
	}

	/**
	 * Returns the database name of a widget type given it's short name and it's field type.
	 */
	function widget_type_name($field_type, $widget_type)
	{
		switch ($field_type) {
		case "cck_taxonomy_ssu": $widget_type = cck_taxonomy_ssu_widget_type_name($widget_type); break;
		case "nodereference": $widget_type = $field_type.'_'.$widget_type; break;
		default: $widget_type = $widget_type;
		}
		return $widget_type;
	}

	/**
	 * Returns true if a CCK widget type with name $widget_type exists for field type $field_type otherwise returns false.
	 */
	function widget_type_exists($field_type, $widget_type)
	{
		$widget_type = widget_type_name($field_type, $widget_type, $straight);
		$widget_types = _content_widget_types();
		return isset($widget_types[$widget_type])?true:false;
	}
	
	/**
	 * Returns CCK informations for widget type $widget_type of field type $field_type.
	 */
	function widget_type_info($field_type, $widget_type)
	{
		$widget_type = widget_type_name($field_type, $widget_type, $straight);
		$widget_types = _content_widget_types();
		return isset($widget_types[$widget_type]) ? $widget_types[$widget_type] : null;
	}
	/**
	 * @}
	 */
	
	/**
	 * @name CCK fields validation routines
	 * 
	 * Each CCK field type has it's own set of settings (the global settings). However the CCK API
	 * is quite low on informations about them - the module provides a form validation routine
	 * which doesn't help us but is enough for CCK itself.
	 * 
	 * 
	 */
	/**
	 * @{
	 */
	
	
	/**
	 * Verifies that field settings (global_settings) array does hold indexes required by
	 * field type.
	 *
	 * First convert the settings SimpleXML tree to an array. If there is a settings validate
	 * handler for this field type then calls it. Otherwise asks module to list the indexes
	 * mandatory for this field type and verifies they exist.
	 *
	 * @return Conversion of SimpleXML settings tree to an array or null if lacking index.
	 */
	function validate_field_settings($field_type, $xml_settings)
	{
		/*
		 * Convert xml representation to an array.
		 */
		$global_settings = plex_simplexml2array($xml_settings);
		/*
		 * If there is a specific handler for this field type jump to it.
		 */
		$handler = $field_type.'_validate';
		// Does this work using 'self' keyword?
		if (method_exists('plex_content', $handler)) {
			return call_user_func(array('plex_content', $handler), $global_settings);
		}
		/*
		 * Verify that settings as the module lists them have their values indicated.
		 * For most CCK field types this should do the trick.
		 */
		$settings_indexes = module_invoke($field_type, 'settings', 'save');
		if (is_array($settings_indexes)) {
			foreach ($settings_indexes as $setting_index) {
				if (!isset($global_settings[$setting_index])) {
					plex::warn("Field type from file '%file' lacks '%setting' setting",
					array('%file'=>$file, '%setting'=>$setting_index));
					return null;
				}
			}
		}
		return $global_settings;
	}
	
	/**
	 * @}
	 */
	
	function require_field($field_name, $dir = null)
	{
		if ($this->field_exists($field_name)) {
			return true;
		}
		if ($dir === null) $dir = './'.drupal_get_path('module', 'ucinstall')."/fields";
		$field_name = str_replace('/', '', $field_name);
		$file = "./${dir}/${field_name}.xml";
		$type = simplexml_load_file($file);
		if ($type === false) {
			plex::warn("Failed to read field definition file '%file'", array('%file'=>$file));
			return false;
		}
		if (!isset($type->type)) {
			plex::warn("Field from '%file' has no given type (nodereference, number...)", array('%file'=>$file));
			return false;
		}
		$type->type = (string)$type->type;
		if (!$this->field_type_exists($type->type)) {
			plex::warn("Field from '%file' has an unknown type '%type' (CCK submodule not installed?)",
			array('%file'=>$file, '%type'=>$type->type));
			return false;
		}

		$global_settings = $this->validate_field_settings($type->type, $type);
		if ($global_settings === null) {
			plex::warn("Field from '%file' has invalid settings",
			array('%file'=>$file));
			return false;
		}
		
		return $this->add_field(
			$name, $type->type, $global_settings,
			isset($type->required)?(int)(bool)$type->required:0,
			isset($type->multiple)?(int)(bool)$type->multiple:0
		);

	}
	
	/**
	 * Creates a CCK field definition.
	 *
	 * As for similar data administration API it takes a name indexed array as $form_values argument.
	 * Contrary to raw CCK form validation it does not create a field instance to stode default widget
	 * values. This should be ok as long as CCK itself does not recreate a default field instance
	 * with empty widget options for a widget type which does not handle this situation.
	 *
	 * @arg $field_name unstraightened field name
	 * @arg $field_type which kind of cck field to use
	 * @arg $global_settings array of settings to use for all further instances of this field (see below)
	 * @arg $required 0/1
	 * @arg $multiple 0/1
	 * @arg $straight internal and unused: is $field_name already straightened or not ?
	 * 
	 * db_storage is initialised to CONTENT_PER_TYPE. Appropriate value will be decided upon first
	 * instance creation: If the module handling widget for the field instance indicates there is columns
	 * to add so db_storage will be updated to CONTENT_PER_FIELD.
	 * 
	 */
	function add_field($field_name, $field_type, array $global_settings = array(), $required = 0, $multiple = 0, $straight = false)
	{
		if ($this->field_exists($field_name, $straight)) {
			plex::notice("plex_content::add_field(): %field_name already exists (or db fucked up)",
			array('%field_name'=>$field_name));
			$field_name = $this->new_field_name($field_name, $straight);
		} else {
			$field_name = $straight ? $field_name : field_name($field_name);
		}
		$field_type = explode('-', $field_type);
		$field_type = $field_type[0];
	
		if ($multiple) {
			$db_storage = CONTENT_DB_STORAGE_PER_FIELD;
		} else {
			$db_storage = CONTENT_DB_STORAGE_PER_CONTENT_TYPE;
		}
	
		db_query("INSERT INTO {node_field} (
			field_name,
			type,
			global_settings,
			required,
			multiple,
			db_storage
		) VALUES ('%s', '%s', '%s', %d, %d, %d)",
			$field_name,
			$field_type,
			serialize($global_settings),
			(int)(boolean)$required,
			(int)(boolean)$multiple,
			$db_storage
		);
		
		content_clear_type_cache();
		
		// Create new database columns as necessary.
		$field_types = _content_field_types();
		$field_type = $field_types[$field_type];
		$field = content_fields($field_name);
		
		$columns = module_invoke($field_type['module'], 'field_settings', 'database columns', $field);
		if (is_array($columns) && count($columns)) {
			content_alter_db_field(array(), array(), $field, $columns);
		}
		
		$setting_names = module_invoke($field_type['module'], 'field_settings', 'save', $field);
		if (is_array($setting_names)) {
			$field_settings = array_merge($setting_names, $field_settings);
		}
	
		db_query("update {node_field} set global_settings='%s'", serialize($field_settings));
	
		plex::notice('Created field %field_name.', array('%field_name'=>$form_values['field_name']));
		return true;
	}
	
	/**
	 * field_name
	 */
	function rem_field($field_name)
	{
		return content_field_delete(array('field_name'=>field_name($field_name)));	
	}

	function field_instance_xml2array($xml)
	{
		if (!isset($xml->field_name)) {
			plex::warn("Not adding field instance to node type %node_type:".
			" field_name is not indicated", array('%node_type'=>$this->name));
			return null;
		}
		$name = (string)$xml->field_name;
	
		if (!isset($xml->widget)) {
			plex::warn("Not adding field %field_name to node type %node_type:".
			" widget is not indicated", array('%node_type'=>$this->name,
			'%field_name'=>$name));
			return null;
		}
		$widget = (string)$xml->widget;

		//if (!field_exists($name)) {
		//	if (false === add_field_file($name)) return false;
		//}

		$array = array(
			'field_name'=>$name,
			'widget_type'=>$widget,
			'properties'=>array(
				'label'=>isset($xml->label)?(string)$xml->label:$name,
				'weight'=>isset($xml->weight)?(int)$xml->weight:0,
				'description'=>isset($xml->description)?$xml->description:'',
				'display_settings'=>isset($xml->display)?array(
					'label'=>array('format'=>isset($xml->display->label)?(string)$xml->display->label:'above'),
					'teaser'=>array('format'=>isset($xml->display->teaser)?(string)$xml->display->teaser:'default'),
					'full'=>array('format'=>isset($xml->display->full)?(string)$xml->display->full:'default'),
					):array(
					'label'=>array('format'=>'above'),
					'teaser'=>array('format'=>'default'),
					'full'=>array('format'=>'default'),
					),
				'widget_settings'=>isset($xml->settings)?plex::simplexml2array($xml->settings):null
			)
		);
		if (isset($xml->default_value_php)) {
			$array['properties']['default_value_php'] = (string)$xml->default_value_php;
		} elseif (isset($xml->default_value)) {
			$array['properties']['default_value'] = (string)$xml->default_value;
		}
		return $array;
	}

	/**
	 * Adds a field instance to a defined content type.
	 * 
	 * @arg $type_name content type name which will hold field instance
	 * @arg $field_name field name to create an instance from (unstraightened)
	 * @arg $widget_type widget type name to use for field
	 * @arg $properties name indexed array of other additional properties (see below)
	 *
	 * $properties can hold these keys:@n
	 * @li 'weight' (defaults to 0) sorting weight in the node type's field list
	 * @li 'label' (defaults to $field_name) field instance's "title"
	 * @li 'display_settings' (defaults to sane values) how to respectively show field instance's title and value (more below)
	 * @li 'description' (defaults to '') field instance's description
	 * @li 'widget_settings' (defaults to unset) depends on the type of widget (more below)
	 * @li 'default_value' (defaults to unset) putted as field instance value before user is asked to set it (more below)
	 * @li 'default_value_php' (defaults to unset) evaluated as field instance value before user is asked to set it (more below)
	 * 
	 * 
	 * 
	 */
	function add_field_instance($type_name, $field_name, $widget_type, $properties)
	{
		$type  = content_types($type_name);
		if ($type === null) {
			plex::warn("plex_content::add_field_instance(): type %type_name doesn't exist",
			array('%type_name'=>$type_name));
			return false;
		}
		$properties['type_name'] = $type_name;

		if (!$this->field_exists($field_name)) {
			plex::warn("plex_content::add_field_instance(): field %field_name doesn't exist",
			array('%field_name'=>$field_name));
			return false;
		}
		$properties['field_name'] = $this->field_name($field_name);
		$field = $this->field_info($field_name);

		if (!$this->widget_type_exists($field['type'], $widget_type)) {
			plex::warn("plex_content::add_field_instance(): widget type %widget_type doesn't exist for %field_type",
			array('%widget_type'=>$widget_type, '%field_type'=>$field['type']));
			return false;
		}
		$properties['widget_type'] = $this->widget_type_name($field['type'], $widget_type);
		$widget_type = $this->widget_type_info($field['type'], $widget_type);

		if (!plex::check_array($properties, array(
			'weight'=>0,
			'label'=>$field_name,
			'display_settings'=>array('label'=>array('format'=>'above'), 'teaser'=>array('format'=>'default'), 'full'=>array('format'=>'default')),
			'description'=>'',
			'widget_settings'=>array())))
		{
			plex::warn("plex_content::add_field_instance(): invalid properties");
			return false;
		}
		
		$module = field_type_module($field['type']);

		$columns = module_invoke($module, 'field_settings', 'database columns', $field);
		
		if (is_array($columns) && count($columns)) {
			if ($field['db_storage'] == CONTENT_DB_STORAGE_PER_CONTENT_TYPE) {
				$new_field = $field;
				$new_field['db_storage'] = CONTENT_DB_STORAGE_PER_FIELD;
				db_query("UPDATE {node_field} SET db_storage = %d WHERE field_name = '%s'", CONTENT_DB_STORAGE_PER_FIELD, $properties['field_name']);
				content_alter_db_field($field, $columns, $new_field, $columns);
			}
		}
		// If content.module is handling the default value,
		// initialize $widget_settings with default values,
		if (content_handle('widget', 'default value', $field) == CONTENT_CALLBACK_DEFAULT) {
			$widget_settings = array_merge($widget_settings, array(
			'default_value' => $properties['default_value'],
			'default_value_php' => $properties['default_value_php'],
			));
		}
		
		// I do believe it is important to call widget known module for widget related things
		// and field type known module for field only related things. Maybe this way it is
		// possible for a module to extend a field type module with a new kind of widget
		$setting_names = module_invoke($widget_type['module'], 'widget_settings', 'save', $field);
		if (is_array($setting_names)) {
			foreach ($setting_names as $setting) {
				if (!array_key_exists($widget_settings, $setting)) {
					plex::warn("plex_content::add_field_instance(): widget settings %setting is not precised",
					array('%setting'=>$setting));
					// todo: so if there was 0 instances we incorrectly updated db_storage,
					// on the other hand 'field' is maybe more accurate when we get the settings
					// list
					return false;
				}
			}
		}
		//$field_settings = array();
		//$setting_names = module_invoke($module, 'field_settings', 'save', $field);
		//if (is_array($setting_names)) {
		//	foreach ($setting_names as $setting) {
		//		$field_settings[$setting] = $properties[$setting];
		//	}
		//}
		
		db_query("INSERT INTO {node_field_instance} (
			field_name,
			type_name,
			weight,
			label,
			widget_type,
			widget_settings,
			display_settings,
			description
		) VALUES ( '%s', '%s', %d, '%s', '%s', '%s', '%s', '%s')",
			$properties['field_name'],
			$properties['type_name'],
			$properties['weight'],
			$properties['label'],
			$properties['widget_type'],
			serialize($widget_settings),
			serialize($properties['display_settings']),
			$properties['description']
		);
		
		plex::notice("Added field %label.", array('%label' => $properties['label']));
		content_clear_type_cache();
		return true;
		
		// If content.module is handling the default value,
		// initialize $widget_settings with default values,
		//if (content_handle('widget', 'default value', $field) == CONTENT_CALLBACK_DEFAULT) {
		//	$widget_settings = array(
		//	'default_value' => $properties['default_value'],
		//	'default_value_php' => $properties['default_value_php'],
		//	);
		//}
	
		//$setting_names = module_invoke($widget_type['module'], 'widget_settings', 'save', $field);
		//if (is_array($setting_names)) {
		//	foreach ($setting_names as $setting) {
		//		$widget_settings[$setting] = $properties[$setting];
		//	}
		//}
		//$field_settings = array();
		//$setting_names = module_invoke($module, 'field_settings', 'save', $field);
		//if (is_array($setting_names)) {
		//	foreach ($setting_names as $setting) {
		//		$field_settings[$setting] = $properties[$setting];
		//	}
		//}
		//return 'admin/content/types/'. $type['url_str'] .'/fields';
	
	//	$prev_field = $field;
	//	$prev_columns = module_invoke($module, 'field_settings', 'database columns', $field);
	//	
		//db_query("UPDATE {node_field_instance} SET
		//	weight = %d,
		//	label = '%s',
		//	widget_type = '%s',
		//	widget_settings = '%s',
		//	description = '%s'
		//WHERE type_name = '%s' AND field_name = '%s'",
		//	$properties['weight'],
		//	$properties['label'],
		//	$properties['widget_type'],
		//	serialize($widget_settings),
		//	$properties['description'],
		//	$properties['type_name'],
		//	$properties['field_name']
		//);
	//	
	//	if ($properties['multiple']) {
	//		$field['db_storage'] = CONTENT_DB_STORAGE_PER_FIELD;
	//	} else {
	//		$instances = db_result(db_query("SELECT COUNT(*) FROM {node_field_instance} WHERE field_name = '%s'", $properties['field_name']));
	//		if ($instances == 1) {
	//			$field['db_storage'] = CONTENT_DB_STORAGE_PER_CONTENT_TYPE;
	//		}
	//	}
	//	db_query("UPDATE {node_field} SET
	//		required = %d,
	//		multiple = %d,
	//		global_settings = '%s',
	//		db_storage = %d
	//	WHERE field_name = '%s'",
	//		$properties['required'],
	//		$properties['multiple'],
	//		serialize($field_settings),
	//		$field['db_storage'],
	//		$properties['field_name']
	//	);
	//	
	//	drupal_set_message(t('Saved field %field.', array('%field' => $properties['label'])));
	//	content_clear_type_cache();
	//	
	//	$new_field = content_fields($properties['field_name']);
	//	$new_columns = module_invoke($module, 'field_settings', 'database columns', $new_field);
	//	
	//	if (!isset($prev_columns)) { $prev_columns = array(); }
	//	if (!isset($new_columns)) { $new_columns = array(); }
	//	content_alter_db_field($prev_field, $prev_columns, $new_field, $new_columns);
	//	
	//	return 'admin/content/types/'. $type['url_str'] .'/fields';
	}

	/**
	 * Automatically removes field if this is it's last instance.
	 * field_name, type_name
	 */
	function rem_field_instance($type_name, $field_name, $straight = false)
	{
		$straight_name = $straight ? $field_name : field_name($field_name);
		$type = content_types($type_name);
		$field = $type['fields'][$straight_name];
		
		if ($type && $field) {
			content_field_instance_delete(array('type_name'=>$type_name, 'field_name'=>$straight_name));
	  		content_clear_type_cache();
			plex::notice('Removed field %field from %type.', array('%field' => $field_name, '%type' => $type_name));
			return true;
		}
		return false;
	}
	
}
/**
 * @}
 */
/**
 * @page plex_content_page Content (CCK) contrib module configuration formats
 * 
 * The content module permits to add custom type of fields to the node types of Drupal.
 * E.G. For an 'item' node type you would like to add a 'value' numeric field and an
 * optional 'ancestor' nodereference field. Before CCK you had to make a module to
 * handle the 'item' node type.
 *
 * @section plex_content_data_types CCK Data Types
 * 
 * CCK deals with 5 kind of objects:
 * @li <tt><strong>Field Type</strong></tt>: Class of the future fields from this type (number, string, date,
 * nodereference, cck_taxonomy_ssu...). It defines the storage nature, properties and
 * methods (outside what the widget will set and override). It's defined by CCK submodules
 * @li <tt><strong>Field</strong></tt>: Field definition, including the type to use, the name, how will be
 * stored values, default...
 * @li <tt><strong>Field Instance</strong></tt>: For a specific node type an instance of a previously defined
 * field
 * @li <tt><strong>Widget Type</strong></tt>: One of the class of possible user interaction a particular
 * field type does know. It's defined by CCK submodules
 * @li <tt><strong>Widget</strong></tt>: Each field instance does use one of the possible widget for it's
 * field type
 *
 * @section plex_content_global_settings Fields Global Settings
 * 
 * Each field type has it's own list of particular configuration settings that fields (definitions) do
 * set using the @c settings tag in XML or using the @c $properties argument in PHP.
 * 
 * You can put anything in the XML @c settings tag as in the @c $properties array. However plex does
 * try to enforce you respect the list each field type does recognize.
 * 
 * here follows for some common field types their list of settings expressed as PHP and XML:
 * @subsection plex_content_nodereference The nodereference Field Type
 * -# In PHP array notation: @verbatim array(
	'referenceable_types'=>array('page'=>'page','story'=>'story'),
	'advanced_view'=>'--',
	'advanced_view_args'=>''
)@endverbatim
 * -# As XML as in a configuration file: @verbatim <settings>
	<referenceable_types>
		<page>page</page>
		<story>story</story>
	</referenceable_types>
	<advanced_view>--</advanced_view>
	<advanced_view_args></advanced_view_args>
</settings>@endverbatim
 * @subsection plex_content_number_integer The number_integer Field Type
 * -# In PHP array notation: @verbatim array(
	'prefix'=>"$",
	'suffix'=>"m^2|ms^2",
	'append_position'=>null,
	'min'=>0,
	'max'=>20,
	'allowed_values'=>"1|one\n2|two\n3|three\n",
	'allowed_values_php'=>""
)@endverbatim
 * -# As XML as in a configuration file: @verbatim <settings>
	<prefix>$</prefix>
	<suffix>m^2|ms^2</suffix>
	<append_position></append_position>
	<min>0</min>
	<max>20</max>
	<allowed_values>1|one&#10;2|two&#10;3|three&#10;</allowed_values>
	<allowed_values_php></allowed_values_php>
</settings>@endverbatim
 * @subsection plex_content_number_decimal The number_decimal Field Type
 * -# In PHP array notation: @verbatim array(
	'prefix'=>"(",
	'suffix'=>")",
	'append_position'=>null,
	'min'=>"0.00000001",
	'max'=>"0.99999999",
	'allowed_values'=>"",
	'allowed_values_php'=>""
)@endverbatim
 * -# As XML as in a configuration file: @verbatim <settings>
	<prefix>(</prefix>
	<suffix>)</suffix>
	<append_position></append_position>
	<min>0.00000001</min>
	<max>0.99999999</max>
	<allowed_values></allowed_values>
	<allowed_values_php></allowed_values_php>
</settings>@endverbatim
 * @subsection plex_content_computed The computed Field Type
 * -# In PHP array notation: @verbatim array(
	'code'=>'
		$tmp = (mt_rand(1, 34) * $node->field_testnumber[0]["value"]) % 4;
		$array = array(
			"Hearts",
			"Spades",
			"Diamonds",
			"Clubs"
		);
		$node_field[0]["value"] = $array[$tmp];',
	'display'=>1,
	'display_format'=>'$display = "<a '.
		'href=\'/themes/garland/images/${node_field_item[\'value\']}.png\' '.
		'title=\'${node_field_item[\'value\']}\'/>";',
	'store'=>1,
	'datatype'=>"varchar",
	'data_length'=>10,
	'data_not_null'=>1,
	'data_default'=>"'Hearts'",
	'data_sortable'=>1
)@endverbatim
 * -# As XML as in a configuration file: @verbatim <settings>
	<code>
		$tmp = (mt_rand(1, 34) * $node-&gt;field_testnumber[0]["value"]) % 4;
		$array = array(
			"Hearts",
			"Spades",
			"Diamonds",
			"Clubs"
		);
		$node_field[0]["value"] = $array[$tmp];
	</code>
	<display>1</display>
	<display_format>
		$display = "<a 
			href='/themes/garland/images/${node_field_item['value']}.png' 
			title='${node_field_item['value']}'/>";
	</display_format>
	<store>1</store>
	<datatype>varchar</datatype>
	<data_length>10</data_length>
	<data_not_null>1</data_not_null>
	<data_default>'Hearts'</data_default>
	<data_sortable>1</data_sortable>
</settings>@endverbatim
 * @subsection plex_content_cck_taxonomy_ssu The cck_taxonomy_ssu Field Type
 * -# In PHP array notation: @verbatim array(
	'top_level_freetag'=>0,
	'no_freetag_branching'=>0,
	'parents'=>0,
	'tags'=>1,
	'rate_yesno'=>1,
	'choices'=>"G\nPG\nPG-13",
	'choice_title'=>'Ratings'
)@endverbatim
 * -# As XML as in a configuration file: @verbatim <settings>
	<top_level_freetag>0</top_level_freetag>
	<no_freetag_branching>0</no_freetag_branching>
	<parents>0</parents>
	<tags>1</tags>
	<rate_yesno>1</rate_yesno>
	<choices>G&#10;PG&#10;PG-13</choices>
	<choice_title>Ratings</choice_title>
</settings>@endverbatim
 * @subsection plex_content_fivestar The fivestar Field Type
 * -# In PHP array notation: @verbatim array(
	'stars'=>4,
	'dynamic_target'=>'',
	'php_target'=>'',
	'axis'=>"Colors"
)@endverbatim
 * -# As XML as in a configuration file: @verbatim <settings>
	<stars>4</stars>
	<dynamic_target></dynamic_target>
	<php_target></php_target>
	<axis>Colors</axis>
</settings>@endverbatim
 * 
 * 
 * @section plex_content_display_settings Fields Display Settings
 * 
 * The display_settings key to $properties defaults to:
 * @verbatim {
	[label] = { [format] = above },
	[teaser] = { [format] = default },
	[full] = { [format] = default }
}@endverbatim
 * 
 * 
 * @section plex_content_widget_settings Field Instances Widget Settings
 * 
 * Here follows some @c widget_settings for commonly used field types:
 * @li @c nodereference @c select : null with default values
 * @li @c nodereference @c autocomplete : null with default values
 * @li @c number_integer @c number : null with default values
 * @li @c number_decimal @c number : null with default values
 * @li @c computed @c computed : null
 * @li @c cck_taxonomy_ssu @c "VocabularyName" : null
 * @li @c fivestar @c stars : allow_clear (0|1) with default values
 * 
 * 
 * @section plex_content_default_value Field Instances Default Values
 * 
 * This is how to set the @c default_value key to $properties (taken from drupal documentation):
 * <pre>array(
 *	0 => array('nid' => value for nid),
 *	// You'll usually want to stop here. Provide more values
 *	// if you want your 'default value' to be multi-valued :
 *	1 => array('nid' => value for nid),
 *	2 => ...
 *);</pre>
 * And this is how to set the @c default_value_php key to $properties :)
 * <pre>"array(
 *	0 => array('nid' => value for nid),
 *	// You'll usually want to stop here. Provide more values
 *	// if you want your 'default value' to be multi-valued :
 *	1 => array('nid' => value for nid),
 *	2 => ...
 *);"</pre>
 *
 * @c nodereference @c select :@n
 * 0 => array('nid'=> "0", 'error_field'=>"field_mybigtest][nids")@n
 * @c fivestar @c stars :@n
 * 0 => array('rating'=>60, 'target'=>null, 'axis'=>null)@n
 * 
 * @section plex_content_examples Full Configuration Samples
 * 
 * @subsection plex_content_example_field Field Description
 * 
 * @verbinclude serie.xml
 * 
 * @subsection plex_content_example_field_instance Field Instance Description
 * 
 * Field instances are tightly associated to node types. As such please refer
 * to the node type @ref plex_node_examples.
 */
